package mbsl.commons.base;

import java.util.List;

import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.graphics.Point;

import mbsl.commons.lib.BasicElement;
import mbsl.commons.lib.Location;
import mbsl.main.StatusBarController;
import mbsl.storage.Database;
import mbsl.system.Core;
import mbsl.system.TextProvider;
import mbsl.ui.Combo;
import mbsl.util.Delayer.SWTDelayer;

/**
 * The base ElementEditController
 */
public abstract class ElementEditController {
	protected ElementEditController(Database pDatabase) {
		mDatabase = pDatabase;
	}
	/** Returns the {@link BasicElementController}&lt;Location&gt; shared between instances. */
	protected BasicElementController<Location> getLocationController() {
		if (mLocationController == null)
			mLocationController = new BasicElementController<Location>(mDatabase, Location.class);
		
		return mLocationController;
	}
	
	/** 
	 * The controller for Advanced Search functionality.
	 * <p>
	 * Instances attach themselves to a {@link Combo}, and expand its functionality by providing an
	 * Advanced Search feature, triggered when the user writes {@value #TRIGGER} as the first content
	 * in the text field. Words after {@value Trigger} are used as search arguments.
	 * <p>
	 * The message keys used in the process are:
	 * <li>advSearch.waiting = The message to post when waiting for user input
	 * <li>advSearch.searching = The message to post while searching
	 * <li>advSearch.success = The message to post when we found the matching item
	 * <li>advSearch.noResult = The message to post when we found no matching items
	 * <li>advSearch.manyResults = The message to post when we found several matching items, with %d where the number of hits should go
	 * <li>advSearch.aborted = The message to post if the search was aborted
	 * <li>advSearch.tooltip = The Combo's tool tip text
	 * </ul>
	 * @see #AdvancedSearchController(Core, Class, Combo, StatusBarController, TextProvider, String)
	 */
	protected class AdvancedSearchController<T extends BasicElement> implements ModifyListener, DisposeListener {
		/** The trigger for the Advanced Search */
		public static final String TRIGGER = ":";
		/**
		 * Creates a new Advanced Search Controller.
		 * @param pCore A Core instance which can provide Database and Display access.
		 * @param pClass The Class of the type of Element we're looking for
		 * @param pCombo The Combo to which this controller shall attach itself.
		 * @param pSBC The StatusBarController to use to give messages to the user.
		 * @param pTP The TextProvider to use to get these messages.
		 * @param pSearchArguments The arguments used for the search.
		 * <p>
		 * The complete HQL search string will be: <br>
		 * {@code from [class] where [search arguments] (and [search arguments] and ...) }<br>
		 * where {@code [search arguments]} could be something like {@code name like '%%%s%%'} for example.
		 * The search string will be run through String.format, so all percentage signs (%) must be double
		 * (%%). The word to search for will be the first object sent, so to insert it once, put %s, and if
		 * you need to insert it several times, put %1$s each place it should be put.
		 * <p>
		 * Examples:<br>
		 * Generic: {@code lower(name) like '%%%s%%'}<br>
		 * For Books: {@code (lower(name) like '%%%1$s%%' or lower(author.firstName) like '%%%1$s%%' or lower(author.surname) like '%%%1$s%%')}
		 */
		public AdvancedSearchController(Core pCore, Class<T> pClass, Combo pCombo, StatusBarController pSBC, TextProvider pTP, String pSearchArguments) {
			mCombo = pCombo;
			sbc = pSBC;
			tp = pTP;
			mCore = pCore;
			mSearchArguments = pSearchArguments;
			mClass = pClass;
			
			mCombo.getWidget().addDisposeListener(this);
			mCombo.getWidget().addModifyListener(this);
			mCombo.getWidget().setToolTipText(tp.get("advSearch.tooltip"));
		}
		
		@Override
		public void modifyText(ModifyEvent pE) {
			// If the field doesn't start with ":", we're not interested.
			// Unless we're already searching for something, in which case we should clean up.
			if (!mIsActive && !mCombo.getWidget().getText().startsWith(TRIGGER))
				return;
			
			if (mDelayer == null || !mDelayer.isStarted())
				sbc.setInfo(tp.get("advSearch.waiting"));

			if (mDelayer == null) {
				mDelayer = new SWTDelayer(new Runnable() {
					@Override
					public void run() {
						mIsActive = true;
						// If the user had triggered the system, but removed it before we could search
						if (!mCombo.getWidget().getText().startsWith(TRIGGER)) {
							// In which case we post the aborted message
							sbc.finishAndRelease(tp.get("advSearch.aborted"));
							mIsActive = false;
							
							return;
						// If the user has triggered the system, but hasn't come up with something to search for yet
						} else if (mCombo.getWidget().getText().trim().equals(TRIGGER))
							// We just wait
							return;
						
						sbc.setInfo(tp.get("advSearch.searching"));
						
						String[] words = mCombo.getWidget().getText().substring(1).split("\\s");
						
						StringBuilder query = new StringBuilder("where ");
						
						for (int i = 0; i < words.length; i++) {
							String word = words[i].toLowerCase();
							
							query.append(' ');
							query.append(String.format(mSearchArguments, word));

							if (i < words.length - 1)
								query.append(" and ");
						}

						if (mElement.getName() != null) {
							query.append(" and name != '");
							query.append(mElement.getName().replaceAll("'", "''"));
							query.append("'");
						}
						
						mHitList = mCore.getDatabase().getList(mClass, query.toString());

						if (mHitList.size() == 1) {
							mIsActive = false;

							mCombo.setSelected(mHitList.get(0));
							// In case we got it all wrong, we should select the text we insert, so it's easy to remove
							mCombo.getWidget().setSelection(new Point(0, mCombo.getWidget().getText().length()));
							
							sbc.finishAndRelease(tp.get("advSearch.success"));
						} else if (mHitList.size() == 0)
							sbc.finishAndRelease(tp.get("advSearch.noResult"));
						else
							sbc.finishAndRelease(tp.get("advSearch.manyResults"), mHitList.size());
					}
				}, 400, mCore.getDisplay());
				
				mDelayer.start();
			} else if (mDelayer.isStarted())
				mDelayer.interrupt();
			else
				mDelayer.start();
		}
		@Override
		public void widgetDisposed(DisposeEvent pE) {
			if (mDelayer != null)
				mDelayer.dispose();
			
			mDelayer = null;
		}
		/** Returns the current element */
		public T getElement() {
			return mElement;
		}
		/** Sets the current element */
		public void setElement(T pElement) {
			mElement = pElement;
		}
		private List<T> mHitList;
		private SWTDelayer mDelayer;
		private boolean mIsActive;
		private Combo mCombo;
		private StatusBarController sbc;
		private TextProvider tp;
		private Core mCore;
		private T mElement;
		private String mSearchArguments;
		private Class<T> mClass;
	}	
	private static BasicElementController<Location> mLocationController;
	
	private Database mDatabase;
}
